package com.example.demo.config;

import com.alibaba.fastjson.JSONObject;
import com.example.demo.handler.CustomExpiredSessionStrategy;
import com.example.demo.handler.ImageCodeFilter;
import com.example.demo.handler.JwtAuthenticationFilter;
import com.example.demo.handler.MyAuthenctiationSuccessHandler;
import com.example.demo.service.SysUserDetailsServiceImpl;
import com.example.demo.util.JwtTokenUtil;
import com.example.demo.util.ResultModel;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.security.Security;


/**
 * 
 * Description:安全配置
 * @author huangweicheng
 * @date 2019/10/21   
*/ 
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    /**
     * 日志记录
     */
    private static final Logger log = LoggerFactory.getLogger(Security.class);

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    protected SysUserDetailsServiceImpl sysUserDetailsService;

    @Autowired
    private ImageCodeFilter imageCodeFilter;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private SessionRegistry sessionRegistry;

    @Autowired
    private DataSource dataSource;

    //会话失效(账号被挤下线)处理逻辑
    @Autowired
    private CustomExpiredSessionStrategy customExpiredSessionStrategy;

    @Autowired
    private MyAuthenctiationSuccessHandler MyAuthenctiationSuccessHandler;
    /**
     *
     * Description:资源角色配置登录
     * @param http
     * @author huangweicheng
     * @date 2019/10/21   
    */
    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        /*图片验证码过滤器设置在密码验证之前*/
        http.csrf().disable().addFilterBefore(imageCodeFilter, UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/", "/*.html", "favicon.ico", "/**/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
                .antMatchers("/user/**","/login").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/hwc/**").hasRole("USER")
                .anyRequest().authenticated()
                .and().formLogin().loginPage("/login").loginProcessingUrl("/user/login")
                /*自定义登录成功处理，返回token值*/
//                .successHandler((HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication)->
//                {
//                    log.info("用户为====>" + httpServletRequest.getParameter("username") + "登录成功");
//                    httpServletResponse.setContentType("application/json;charset=utf-8");
//                    /*获取用户权限信息*/
//                    UserDetails userDetails = (UserDetails) authentication.getPrincipal();
//                    String token = jwtTokenUtil.generateToken(userDetails);
//                    /*存储redis并设置了过期时间*/
//                    redisTemplate.boundValueOps(userDetails.getUsername() + "yl").set(token,10, TimeUnit.MINUTES);
//                    JSONObject jsonObject = new JSONObject();
//                    jsonObject.put("code", ResultModel.SUCCESS);
//                    jsonObject.put("msg","登录成功");
//                    /*认证信息写入header*/
//                    httpServletResponse.setHeader("Authorization",token);
//                    httpServletResponse.getWriter().write(jsonObject.toJSONString());
//                })
                .successHandler(MyAuthenctiationSuccessHandler)
                /*登录失败处理*/
                .failureHandler((HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)->
                {
                    log.info("用户为====>" + request.getParameter("username") + "登录失败");
                    String content = exception.getMessage();
                    //TODO 后期改进密码错误方式，统一处理
                    String temp = "Bad credentials";
                    if (temp.equals(exception.getMessage()))
                    {
                        content = "用户名或密码错误";
                    }
                    response.setContentType("application/json;charset=utf-8");
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("code", ResultModel.ERROR);
                    jsonObject.put("msg",content);
                    jsonObject.put("content",exception.getMessage());
                    response.getWriter().write(jsonObject.toJSONString());
                })
                .and().rememberMe()
                .tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(60 * 3)
                .userDetailsService(sysUserDetailsService)//设置userDetailsService
                /*无权限访问处理*/
                .and().exceptionHandling().accessDeniedHandler((HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e)->
                {
                    httpServletResponse.setContentType("application/json;charset=utf-8");
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("code",HttpStatus.FORBIDDEN);
                    jsonObject.put("msg", "无权限访问");
                    jsonObject.put("content",e.getMessage());
                    httpServletResponse.getWriter().write(jsonObject.toJSONString());
                })
                /*匿名用户访问无权限资源时的异常*/
                .and().exceptionHandling().authenticationEntryPoint((HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)->
                {
                    response.setContentType("application/json;charset=utf-8");
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("code",HttpStatus.FORBIDDEN);
                    jsonObject.put("msg","匿名无访问权限");
                    response.getWriter().write(jsonObject.toJSONString());
                })
                //登出
                .and().logout().
                logoutUrl("/logout").
                permitAll().//允许所有用户
                //登出之后删除cookie
                deleteCookies("JSESSIONID")
                .and().sessionManagement().
                invalidSessionUrl("/login.html").
                //同一账号同时登录最大用户数
                maximumSessions(1).
                // 当达到最大值时，是否保留已经登录的用户
                maxSessionsPreventsLogin(false).
                // 当达到最大值时，旧用户被踢出后的操作
                expiredSessionStrategy(customExpiredSessionStrategy).
                sessionRegistry(sessionRegistry);
                /*基于token，所以不需要session*/
//                .and().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                /*token过滤*/
                http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);

    }

    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception
    {
        authenticationManagerBuilder.userDetailsService(sysUserDetailsService).passwordEncoder(new PasswordEncoder()
        {
            /**
             *
             * Description:用户输入的密码加密
             * @param charSequence
             * @author huangweicheng
             * @date 2019/10/21
            */
            @Override
            public String encode(CharSequence charSequence)
            {
                return DigestUtils.md5Hex(charSequence.toString());
            }

            /**
             *
             * Description: 与数据库的密码匹配
             * @param charSequence 用户密码
             * @param encodedPassWord 数据库密码
             * @author huangweicheng
             * @date 2019/10/21
            */
            @Override
            public boolean matches(CharSequence charSequence, String encodedPassWord)
            {
                return encodedPassWord.equals(DigestUtils.md5Hex(charSequence.toString()));
            }
        });
    }
    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        //tokenRepository.setCreateTableOnStartup(true);  //系统在启动的时候生成“记住我”的数据表（只能使用一次）
        return tokenRepository;
    }
    //token过滤器
    @Bean
    public JwtAuthenticationFilter authenticationTokenFilterBean()
    {
        return new JwtAuthenticationFilter();
    }
}